#! /usr/bin/env Python3
# coding=utf-8
"""
天天基金网基金数据爬虫模块
作者: KydGroups
版本: 1.0.0
完成日期: 2024-03-03
修改记录:
"""
import time
import random
from pyquery import PyQuery as pq

from KydPyPub.Sys import TypeConvert #添加字符串转换模块
from KydPyPub.Sys import KydSys #添加系统信息模块

class TTJJFund():
    """
    基金数据网络爬虫类,主要功能:
        1、爬取基金的基本信息
        2、爬取基金当前阶段涨幅
        3、爬取基金分红、拆分数据
        4、爬取基金历史价值数据
        5、攫取基金十大重仓股及十大重仓债券,十大重仓基金没有数据
    """
    def __init__(self):
        """
        :DESCRIPTION:
            初始化函数
        :PARAMS:
            nothing
        :RETURNS:
            nothing
        """
        self.strBaseInfoUrlFormat = "http://fundf10.eastmoney.com/jbgk_%s.html" #天天基金网基本基本信息网址格式化字符串
        self.strTopUrlFormat = "http://fund.eastmoney.com/%s.html" #天天基金网基金主页网址,可查阶段收益及净值
        self.strBonusUrlFormat = "http://fundf10.eastmoney.com/fhsp_%s.html" #基金分红拆分网址
        self.strHoldTenStockUrlFormat = "http://fundf10.eastmoney.com/ccmx_%s.html" #基金持仓十大股票网址
        self.strHoldTenBondUrlFormat = "http://fundf10.eastmoney.com/ccmx1_%s.html" #基金持仓十大债券网址
        self.urlheaders = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36' }
        self.strFundCode = "" #基金代码
        self.strStoreCode = "" #基金存储代码
        self.nDelayMin = 20 #随机暂停最小秒数
        self.nDelayMax = 40 #随机暂停最大秒数


    def SetDelay(self, nMin:int=20, nMax:int=40):
        """
        :DESCRIPTION:
            为了网站防爬虫,设置爬取数据后随机暂停秒数函数,可以由外部调用
        :PARAMS:
            nMin: 最小暂停秒数
            nMax: 最大暂停秒数
        :RETURNS:
            nothing
        """
        if nMin <= 10:
            nMin = 10
        if nMax <= 20:
            nMax = 20
        self.nDelayMin = nMin if (nMin > nMax) else nMax #取最小值
        self.nDelayMax = nMax if (nMax < nMin) else nMin #取最大值


    def __Delay(self):
        """
        :DESCRIPTION:
            为了防止网站禁止爬虫,每一次爬取数据后都要随机暂停
        :PARAMS:
            nothing
        :RETURNS:
            nothing
        """
        nDelay = random.randint(self.nDelayMin, self.nDelayMax)
        print("Fund_TTJJ_Crawer------>>爬取数据随机暂停%d秒,请等待!" % nDelay)
        time.sleep(nDelay)
        return True


    def SetCode(self, strCode:str=""):
        """
        :DESCRIPTION:
            设置要爬取数据的基金代码
        :PARAMS:
            strCode: 基金代码,6位数字
        :RETURNS:
            True:设置成功 False:设置失败
        """
        self.strFundCode = "" #清空保存的基金代码
        self.strStoreCode = "" #清空保存的基金存储代码
        if not strCode: #如果没有传入参数
            return False
        else:
            if len(strCode) == 6: #如果输入的代码是6个
                self.strFundCode = strCode #类的基金代码变量
                self.strStoreCode = "f_" + strCode #类的基金存储代码变量
                return True
            else:#如果输入的代码错误
                return False


    def CrawBaseInfo(self):
        """
        :DESCRIPTION:
            爬取基金基本信息数据
        :PARAMS:
            nothing
        :RETURNS:
            None:没有爬取到数据 Dict:爬取的数据
        """
        try:
            if not self.strFundCode: #如果没有设置基金代码
                print("Fund_TTJJ_Crawer------>>没有设置基金代码")
                return None
            else:
                print("Fund_TTJJ_Crawer------>>正在爬取基金 %s 基本信息!" % self.strFundCode)
                self.__Delay() #随机暂停
                try:
                    url = self.strBaseInfoUrlFormat % self.strFundCode #格式化基金基本信息网址
                    doc = pq(url=url, encoding='utf-8', headers=self.urlheaders, timeout=200)  #获取网页
                except: #如果出现错误，则返回
                    strError = TypeConvert.StrToColorStr(("Fund_TTJJ_Crawer------>>下载基金 %s 基本信息失败!" % self.strFundCode), 'alarm')
                    print(strError)
                    return None
                dictReturn = dict()
                dictReturn['FundCode'] = self.strFundCode #设置基金代码
                dictReturn['StoreCode'] = self.strStoreCode #设置基金存储代码
                dictBase = self.__ResolveBaseInfo(doc) #解析基本信息
                if not dictBase: #如果没有解析成功
                    return None
                else:
                    for key in dictBase.keys():
                        dictReturn[key] = dictBase[key]
                return dictReturn
        except:
            return None


    def __ResolveBaseInfo(self, doc):
        """
        :DESCRIPTION:
            从下载的网页中解析股票基本信息
        :PARAMS:
            doc: 下载的网页对象
        :RETURNS:
            None:解析失败 dict:解析成功
        """
        try:
            if not doc:
                return None
            eleList = list(doc('table[class="info w790"] tr td').items())
            dictResult = dict()
            dictResult["FundName"] = eleList[0].text() #基金全称
            dictResult["FundAbbr"] = eleList[1].text() #基金简称
            strKind = eleList[3].text() #基金类型
            nPos = strKind.find('-') #查找是否有二级分类
            if nPos != -1:
                dictResult['FundKind'] = strKind[:nPos] #基金种类
                dictResult["ClassTwo"] = strKind[nPos+1:] #二级分类
            else:
                dictResult['FundKind'] = strKind
                dictResult["ClassTwo"] = '暂无分类' #二级分类
            dictResult["IssueDate"] = TypeConvert.DateCnToSqlStr(eleList[4].text()) #发行日期
            dictResult["EstaDate"] = TypeConvert.DateCnToSqlStr(eleList[5].text()[:11]) #成立日期
            dictResult["EstaShare"] = TypeConvert.StrTopNumToFloat(eleList[5].text()[13:]) #成立份额
            if dictResult['EstaShare'] is None:#部分量化基金滑成立份额
                dictResult['EstaShare'] = 0.0
            dictResult["RecentSize"] = TypeConvert.StrTopNumToFloat(eleList[6].text()) #当前资产规模
            strShare = eleList[7].text() #当前份额字符
            dictResult["RecentShare"] = TypeConvert.StrTopNumToFloat(strShare) #当前份额
            nPos = strShare.find('：')
            if nPos != -1:
                dictResult["FinaDate"] = TypeConvert.DateCnToSqlStr(strShare[(nPos+1):-1]) #账务日期
            else:
                dictResult["FinaDate"] = "1991-01-01" #账务日期,使用没有开市的日期
            dictResult["Company"] = eleList[8].text() #基金公司
            dictResult["Custodian"] = eleList[9].text() #基金托管人
            dictResult["FundManager"] = eleList[10].text() #基金经理
            listBonus = TypeConvert.StrSplitNumerToList(eleList[11].text()) #获取分红的信息
            if not listBonus:#如果没有查找到信息
                dictResult['BonusValue'] = 0.00 #累计分红金额
                dictResult['BonusTime'] = 0 #分红次数
            else:
                dictResult['BonusValue'] = float(listBonus[0]) #累计分红金额
                dictResult['BonusTime'] = int(listBonus[1]) #分红次数
            dictResult['ManaFeeRate'] = TypeConvert.StrTopNumToFloat(eleList[12].text() ) #管理费率
            dictResult['CustodyRate'] = TypeConvert.StrTopNumToFloat(eleList[13].text() ) #托管费率
            strFee = eleList[14].text() #获取销售服务字段
            nPos = strFee.find('--')
            if nPos != -1:
                dictResult['SellRate'] = 0.00 #销售服务费率,为0
            else:
                dictResult['SellRate'] = TypeConvert.StrTopNumToFloat(strFee) #销售服务费率
            dictResult['UpdateDate'] = KydSys.CurDate() #设置当前日期为数据更新日期
            dictResult['Remark'] = "在市运行" #设置备注
            return dictResult
        except:
            return None


    def CrawBonusData(self):
        """
        :DESCRIPTION
            爬取基金分红折算数据
        :PARAMS:
            nothing
        :RETURNS:
            None:爬取失败 List:爬取成功
        """
        try:
            if not self.strFundCode: #如果没有设置基金代码
                print("Fund_TTJJ_Crawer------>>没有设置基金代码")
                return None
            else:
                print("Fund_TTJJ_Crawer------>>正在爬取基金 %s 分红折算数据!" % self.strFundCode)
                self.__Delay() #随机暂停
                try:
                    url = self.strBonusUrlFormat % self.strFundCode #格式化基金分红折算网址
                    doc = pq(url=url, encoding='utf-8', headers=self.urlheaders, timeout=200)  #获取网页
                except: #如果出现错误，则返回
                    strError = TypeConvert.StrToColorStr(("Fund_TTJJ_Crawer------>>下载基金 %s 分红折算数据失败!" % self.strFundCode), 'alarm')
                    print(strError)
                    return None
                listBonusData = list() #分红送配数据
                trList = list(doc('table[class="w782 comm cfxq"] tbody tr').items()) #获取分红详情
                if not trList:#如果没有获取到数据,则跳过
                    pass
                else:
                    if trList[0].html().find("暂无") != -1:#如果只有一行暂无分红信息
                        pass
                    else:
                        for tr in trList:
                            tdList = list(tr('td').items())
                            dictBonus = dict()
                            dictBonus['StoreCode'] = self.strStoreCode #基金存储代码
                            dictBonus['FundCode'] = self.strFundCode #基金代码
                            dictBonus['RegDate'] = tdList[1].text() #权益登记日
                            dictBonus['DiviDate'] = tdList[2].text() #除息日
                            strValue = tdList[3].text() #分红金额说明字符串
                            listValue = TypeConvert.StrSplitNumerToList(str(strValue))
                            if not listValue:
                                dictBonus['EachDiviValue'] = 0.0 #每份除息值
                            else:
                                dictBonus['EachDiviValue'] = float(listValue[0]) #每份除息值
                            #dictBonus['EachDiviValue'] = float(tdList[3].text()[5:-1]) #每份除息值
                            dictBonus['PayDate'] = tdList[4].text() #分红发放日
                            dictBonus['Remark'] = "分红" #类别
                            listBonusData.append(dictBonus)
                trList = list(doc('table[class="w782 comm fhxq"] tbody tr').items()) #获取拆分详情
                if not trList: #如果没有获取到数据,则跳过
                    pass
                else:
                    if trList[0].html().find("暂无") != -1:#如果只有一行暂无拆分信息
                        pass
                    else:
                        for tr in trList:
                            tdList = list(tr('td').items())
                            dictBonus = dict()
                            dictBonus['StoreCode'] = self.strStoreCode #基金存储代码
                            dictBonus['RegDate'] = tdList[1].text() #权益登记日
                            dictBonus['DiviDate'] = tdList[1].text() #除息日
                            strValue = tdList[3].text() #分红金额说明字符串
                            listValue = TypeConvert.StrSplitNumerToList(str(strValue))
                            if not listValue:
                                dictBonus['EachDiviValue'] = 1.0 #每份除息值
                            else:
                                dictBonus['EachDiviValue'] = float(listValue[0]) #每份除息值
                            #dictBonus['EachDiviValue'] = float(tdList[3].text()[2:]) #每份除息值
                            dictBonus['PayDate'] = tdList[4].text() #分红发放日
                            dictBonus['Remark'] = "折算" #类别
                            listBonusData.append(dictBonus)
                if not listBonusData:#如果列表中没有数据
                    return None
                else:
                    return listBonusData
        except:
            return None


    def CrawStageProfitData(self):
        """
        :DESCRITPION
            爬取基金阶段收益及现价数据
        :PARAMS:
            nothing
        :RETURNS:
            None:爬取失败   Dict:爬取成功 
        """
        if not self.strFundCode: #如果没有设置基金代码
            print("Fund_TTJJ_Crawer------>>没有设置基金代码")
            return None
        else:
            print("Fund_TTJJ_Crawer------>>正在爬取基金 %s 现值及阶段收益数据!" % self.strFundCode)
            self.__Delay() #随机暂停
        try:
            url = self.strTopUrlFormat % self.strFundCode #格式化基金阶段收益网址
            doc = pq(url=url, encoding='utf-8', headers=self.urlheaders, timeout=200)  #获取网页
        except: #如果出现错误，则返回
            strError = TypeConvert.StrToColorStr(("从天天基金网下载基金 %s 阶段收益失败!" % self.strFundCode), 'alarm')
            print(strError)
            return None
        spanList= list(doc('div[class="dataOfFund"] dl dd span').items())
        if not spanList: #没有分隔到数据
            return None
        else:
            dictStage = dict() #阶段涨幅数据字典
            dictStage['StoreCode'] = self.strStoreCode #基金存储代码
            dictStage["CurValue"] = TypeConvert.StrTopNumToFloat( str(spanList[0].text()) ) #当前净值
            dictStage["AccuValue"] = TypeConvert.StrTopNumToFloat( str(spanList[6].text()) ) #累计净值
            dictStage["Month"] = TypeConvert.StrTopNumToFloat( str(spanList[3].text()) ) #近一月涨幅
            dictStage["Quarter"] = TypeConvert.StrTopNumToFloat( str(spanList[8].text()) ) #近三月涨幅
            dictStage["HalfYear"] = TypeConvert.StrTopNumToFloat( str(spanList[13].text()) ) #近半年涨幅
            dictStage["Year"] = TypeConvert.StrTopNumToFloat( str(spanList[5].text()) ) #近一年涨幅
            dictStage["ThreeYear"] = TypeConvert.StrTopNumToFloat( str(spanList[10].text()) ) #近三年涨幅
            dictStage["EstaYield"] = TypeConvert.StrTopNumToFloat( str(spanList[15].text()) ) #成立以来涨幅
            dictStage['UpdateDate'] = KydSys.CurDate() #数据更新日期
            return dictStage
